// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2023 HUAWEI TECHNOLOGIES CO., LTD.
 */

#include <linux/file.h>

#include "vmx_hotreplace.h"

void vmx_vfio_hotreplace_pre(struct kvm_vcpu *vcpu, int val)
{
	struct vcpu_vmx *vmx = to_vmx(vcpu);

	/*
	 * val :
	 *     !0(hot-rplace) : stop sync pir to irr
	 *     0(rollback) : recover it, restart to sync
	 * This pir is need to save and restore in the new pir
	 * if the vm is in hot-replace.
	 * So we can not clean it again.
	 */
	vmx->stop_sync_pir = !!val;
}

/*
 * There are 5 parts in the pi_desc:
 *        need to be synchronized?
 * ----------------------------
 * pir         Y
 * ON          Y
 * SN          N
 * NV          N
 * NDST        N
 *
 * pir is represented for vectors and ON is recorded whether there
 * is a pending interrurpt or not.
 *
 * SN/NV indicates if notification events must be suppressed when
 * posting non-urgent interrupts and the physical vector used for the
 * notification event. They are decided by the status of VCPU.
 * Considering that we have already stopped the vm here,
 * the interrupts only can be recorded. So they can not influence
 * interrupts as long as we record all interrupts.
 *
 * NDST is the physical APIC-ID of the logical CPU. But the vcpu
 * in the new qemu maybe in a new logical CPU.
 * So we can not synchronize it.
 */
void sync_pi_to_new(struct pi_desc *old_pi_desc,
			struct pi_desc *new_pi_desc)
{
	int i = 0;
	u32 old_pir_val = 0;
	unsigned long offset = 0;

	/*
	 * step1 : sync pir
	 *
	 * There are 256 vectors. So pir has 256bit(8*u32).
	 */
	for (i = 0; i < 8; i++) {
		/*
		 * The old pir has stopped to recevice interrupts.
		 * So the value of old_pir does not change when
		 * sync it to the new one.
		 */
		old_pir_val = READ_ONCE(old_pi_desc->pir[i]);
		while (old_pir_val) {
			/*
			 * find the first bit of bitmap
			 */
			offset = __fls(old_pir_val);
			/*
			 * Every bit of pir is represented for a vecotr.
			 * vector number = i*4(u32 have 4 bytes)*
			 * 8(one byte have 8 bits)+offset.
			 */
			pi_test_and_set_pir(
				(i * sizeof(new_pi_desc->pir[0]) * 8 + offset),
				new_pi_desc);
			test_and_clear_bit(offset,
				(unsigned long *)&old_pir_val);
		}
	}

	/* step2: sync ON
	 *
	 * If ON is 1, there is a pending interrupt in the old qemu.
	 * We stop the vm first and then get the status of apic in the
	 * old qemu. When we get tht status of apci, the ON will be
	 * cleared(vmx_sync_pir_to_irr).
	 * So ,if the ON is 1 here, the interrupt must be triggered
	 * after stopping the vm. In this case ,this interrput just be
	 * recorded in the pir.So we must do two things:
	 * 1) sync the pir to the new qemu
	 * 2) set the ON in the new qemu if it is 0
	 * If we do not set ON when it is 0, the interrupt triggered
	 * after stopping the vm can not be done until the new qemu set
	 * the ON.
	 */
	if ((pi_test_on(old_pi_desc)) && (!pi_test_on(new_pi_desc)))
		pi_test_and_set_on(new_pi_desc);
}

int vmx_vfio_hotreplace_sync_pi(struct kvm_vcpu *vcpu, int vmfd)
{
	int i = 0, vcpu_id = vcpu->vcpu_id;
	int ret = -1;
	struct pi_desc *pi_desc = &to_vmx(vcpu)->pi_desc;
	struct kvm *old_kvm = NULL;
	struct vcpu_vmx *old_vmx = NULL;
	struct kvm_vcpu *old_vcpu = NULL;
	struct fd f = {0};

	f = fdget(vmfd);
	if (!f.file)
		goto out;

	old_kvm = kvm_get_kvm_from_file(f);
	if (!old_kvm) {
		fdput(f);
		goto out;
	}

	kvm_get_kvm(old_kvm);
	kvm_for_each_vcpu(i, old_vcpu, old_kvm) {
		if (vcpu_id == old_vcpu->vcpu_id) {
			old_vmx = to_vmx(old_vcpu);
			sync_pi_to_new(&old_vmx->pi_desc, pi_desc);
			break;
		}
	}
	kvm_put_kvm(old_kvm);
	fdput(f);
	ret = 0;
out:
	return ret;
}

int vmx_sync_pir_to_irr_with_check(struct kvm_vcpu *vcpu)
{
	struct vcpu_vmx *vmx = to_vmx(vcpu);

	/*
	 * If the vm is in hot-replace, the pir can not be synchronized
	 * to IRR always. Because it need to restore in the new pir.
	 * Check this vcpu whether need to sync or not.
	 */
	if (vmx->stop_sync_pir)
		return 0;

	return vmx_sync_pir_to_irr(vcpu);
}

int vmx_sync_pir_to_irr_without_check(struct kvm_vcpu *vcpu)
{
	return vmx_sync_pir_to_irr(vcpu);
}
